package com.ruoyi.ai.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.dashscope.common.Role;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ai.api.bo.WxChatMessageBo;
import com.ruoyi.ai.api.bo.WxMaConsumeBo;
import com.ruoyi.ai.config.AiConfig;
import com.ruoyi.ai.domain.WxChat;
import com.ruoyi.ai.domain.bo.WxChatBo;
import com.ruoyi.ai.domain.bo.WxDialogueBo;
import com.ruoyi.ai.domain.vo.WxChatVo;
import com.ruoyi.ai.domain.vo.WxDialogueVo;
import com.ruoyi.ai.domain.vo.WxUserVo;
import com.ruoyi.ai.enums.AiChatStatusEnum;
import com.ruoyi.ai.enums.ChangeBizTypeEnum;
import com.ruoyi.ai.factory.ModelChatService;
import com.ruoyi.ai.factory.ModelChatStrategyFactory;
import com.ruoyi.ai.mapper.WxChatMapper;
import com.ruoyi.ai.service.IWxChatService;
import com.ruoyi.ai.service.IWxDialogueService;
import com.ruoyi.ai.service.IWxNumChangeService;
import com.ruoyi.ai.service.IWxUserService;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 聊天记录Service业务层处理
 *
 * @author ruoyi
 * @date 2024-04-01
 */
@Slf4j
@Service
public class WxChatServiceImpl implements IWxChatService {

    @Autowired
    private WxChatMapper baseMapper;

    @Autowired
    private IWxDialogueService wxDialogueService;

    @Autowired
    private LockTemplate lockTemplate;

    @Autowired
    private AiConfig aiConfig;

    @Autowired
    private IWxUserService wxUserService;

    @Autowired
    private IWxNumChangeService wxNumChangeService;

    @Autowired
    @Qualifier("threadPoolTaskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private ISysUserService sysUserService;

    /**
     * 查询聊天记录
     */
    @Override
    public WxChatVo queryById(Long chatId) {
        return baseMapper.selectVoById(chatId);
    }

    /**
     * 查询聊天记录列表
     */
    @Override
    public TableDataInfo<WxChatVo> queryPageList(WxChatBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<WxChat> lqw = buildQueryWrapper(bo);
        Page<WxChatVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询聊天记录列表
     */
    @Override
    public List<WxChatVo> queryList(WxChatBo bo) {
        LambdaQueryWrapper<WxChat> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<WxChat> buildQueryWrapper(WxChatBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<WxChat> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getWxUserId() != null, WxChat::getWxUserId, bo.getWxUserId());
        lqw.eq(bo.getDialogueId() != null, WxChat::getDialogueId, bo.getDialogueId());
        lqw.eq(StringUtils.isNotBlank(bo.getRole()), WxChat::getRole, bo.getRole());
        lqw.eq(bo.getStatus() != null, WxChat::getStatus, bo.getStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getUserType()), WxChat::getUserType, bo.getUserType());
        if (params.containsKey("orderBySort")) {
            String direction = (String) params.get("orderBySort");
            if (direction.equals("asc")) {
                lqw.orderByAsc(WxChat::getSort);
            } else {
                lqw.orderByDesc(WxChat::getSort);
            }
        }
        return lqw;
    }

    /**
     * 新增聊天记录
     */
    @Override
    public Boolean insertByBo(WxChatBo bo) {
        WxChat add = BeanUtil.toBean(bo, WxChat.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setChatId(add.getChatId());
        }
        return flag;
    }

    /**
     * 修改聊天记录
     */
    @Override
    public Boolean updateByBo(WxChatBo bo) {
        WxChat update = BeanUtil.toBean(bo, WxChat.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(WxChat entity) {
        //TODO 做一些数据校验,如唯一约束
        entity.setDelFlag(UserConstants.DEL_FLAG_FALSE);
    }

    /**
     * 批量删除聊天记录
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void chat(WxChatMessageBo messageBo) {
        Long wxUserId = LoginHelper.getUserId();
        UserType userType = LoginHelper.getUserType();
        final String key = "chat_" + wxUserId;
        final LockInfo lockInfo = lockTemplate.lock(key, 10000L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("上次对话还未完成");
        }
        this.checkChatNum(wxUserId, userType);
        //判断对话是否存在
        Long dialogueId = messageBo.getDialogueId();
        WxDialogueVo wxDialogueVo = wxDialogueService.queryById(dialogueId);
        if (wxDialogueVo == null) {
            throw new ServiceException("对话不存在");
        }
        //聊天轮次判断
        List<WxChatVo> wxChatVos = this.queryHistoryNormalList(dialogueId);
        boolean isFirst = false;
        Integer lastSort = 0;
        if (CollectionUtil.isEmpty(wxChatVos)) {
            wxChatVos = new ArrayList<>();
            isFirst = true;
        } else {
            lastSort = wxChatVos.get(wxChatVos.size() - 1).getSort();
        }
        //检测是否已达最大长度，轮次*2是聊天数
        if (wxChatVos.size() >= getMaxRounds()) {
            throw new ServiceException("本次对话已达到最大轮次，请新建一个对话");
        }

        WxChatVo question = createUserWxChatVo(messageBo.getContent(), dialogueId, wxUserId, lastSort,userType);
        wxChatVos.add(question);

        WxChatBo questionBo = transToBo(question);
        //调用具体AI接口获取回复，利用websocket将信息传给前端
        List<WxChatVo> finalWxChatVos = wxChatVos;
        boolean finalIsFirst = isFirst;
        threadPoolTaskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                asyncAnswer(questionBo, finalWxChatVos, wxUserId, dialogueId, finalIsFirst, lockInfo, userType, messageBo.getPrompt());
            }
        });
    }

    /**
     * 异步处理回复
     * 注意，这里一定要传用户类型，因为异步处理的时候，用户信息已经失效了
     */
    public void asyncAnswer(WxChatBo questionBo,
                            List<WxChatVo> wxChatVos,
                            Long wxUserId,
                            Long dialogueId,
                            Boolean isFirst,
                            LockInfo lockInfo,
                            UserType userType,
                            String prompt) {
        //调用AI接口获取回复，利用websocket将信息传给前端
        try {
            ModelChatService modelChatService = ModelChatStrategyFactory.getEnableMfg();
            String answer;
            if (StringUtils.isNotBlank(prompt)) {
                answer = modelChatService.chatStream(wxChatVos, wxUserId, prompt);
            } else {
                answer = modelChatService.chatStream(wxChatVos, wxUserId);
            }
            //保存聊天记录
            WxChatBo answerBo = createAiWxChatBo(answer, dialogueId, wxUserId, questionBo.getSort(),userType);
            insertByBo(questionBo);
            insertByBo(answerBo);
            //扣减次数
            wxNumChangeService.consume(this.buildWxMaConsumeForChat(questionBo.getChatId(), wxUserId,userType.getUserType()));
            //如果是第一次问问题，则将第一次的内容保存到对话里
            if (isFirst) {
                WxDialogueBo wxDialogueBo = new WxDialogueBo();
                wxDialogueBo.setDialogueId(dialogueId);
                wxDialogueBo.setFirstContent(questionBo.getContent());
                wxDialogueService.updateByBo(wxDialogueBo);
            }
        } catch (Exception e) {
            log.error("获取回复失败", e);
            WxChatBo answerBo = createAiWxChatBo(e.getMessage(), dialogueId, wxUserId, questionBo.getSort(),userType);
            answerBo.setStatus(AiChatStatusEnum.ERROR.getType());
            questionBo.setStatus(AiChatStatusEnum.ERROR.getType());
            questionBo.setChatId(null);
            insertByBo(questionBo);
            insertByBo(answerBo);
        } finally {
            lockTemplate.releaseLock(lockInfo);
        }
    }

    @Override
    public List<WxChatVo> queryHistoryNormalList(Long dialogueId) {
        WxChatBo wxChatBo = new WxChatBo();
        wxChatBo.setDialogueId(dialogueId);
        wxChatBo.setStatus(AiChatStatusEnum.NORMAL.getType());
        //获取历史对话列表
        Map<String, Object> params = wxChatBo.getParams();
        params.put("orderBySort", "asc");
        return queryList(wxChatBo);
    }

    private WxMaConsumeBo buildWxMaConsumeForChat(Long bizId, Long wxUserId,String userType) {
        WxMaConsumeBo wxMaConsumeBo = new WxMaConsumeBo();
        wxMaConsumeBo.setBizType(ChangeBizTypeEnum.AI_CHAT.getType());
        wxMaConsumeBo.setBizId(bizId);
        wxMaConsumeBo.setConsumeNum(1);
        wxMaConsumeBo.setWxUserId(wxUserId);
        wxMaConsumeBo.setUserType(userType);
        return wxMaConsumeBo;
    }

    public WxChatVo createUserWxChatVo(String content, Long dialogueId, Long wxUserId, Integer lastSort, UserType userType) {
        WxChatVo wxChatVo = new WxChatVo();
        wxChatVo.setRole(Role.USER.getValue());
        wxChatVo.setContent(content);
        wxChatVo.setDialogueId(dialogueId);
        wxChatVo.setWxUserId(wxUserId);
        wxChatVo.setStatus(AiChatStatusEnum.NORMAL.getType());
        wxChatVo.setSort(lastSort + 1);
        wxChatVo.setUserType(userType.getUserType());
        return wxChatVo;
    }

    public WxChatBo createAiWxChatBo(String content, Long dialogueId, Long wxUserId, Integer lastSort,UserType userType) {
        WxChatBo wxChatBo = new WxChatBo();
        wxChatBo.setWxUserId(wxUserId);
        wxChatBo.setRole(Role.ASSISTANT.getValue());
        wxChatBo.setContent(content);
        wxChatBo.setDialogueId(dialogueId);
        wxChatBo.setStatus(AiChatStatusEnum.NORMAL.getType());
        wxChatBo.setSort(lastSort + 1);
        wxChatBo.setUserType(userType.getUserType());
        return wxChatBo;
    }

    private WxChatBo transToBo(WxChatVo wxChatVo) {
        WxChatBo wxChatBo = new WxChatBo();
        BeanCopyUtils.copy(wxChatVo, wxChatBo);
        return wxChatBo;
    }

    private void checkChatNum(Long wxUserId) {
        WxUserVo wxUserVo = wxUserService.queryById(wxUserId);
        Integer chatNum = wxUserVo.getChatNum();
        if (chatNum <= 0) {
            throw new ServiceException("您的AI聊天次数已用完，请先购买");
        }
    }

    private void checkChatNum(Long wxUserId, UserType userType) {
        if (userType == UserType.WX_CUSTOMER) {
            checkChatNum(wxUserId);
        } else {
            SysUser sysUser = sysUserService.selectUserById(wxUserId);
            if (sysUser.getChatNum() == null || sysUser.getChatNum() <= 0) {
                throw new ServiceException("您的AI聊天次数已用完，请先购买");
            }
        }
    }

    private int getMaxRounds() {
        if (aiConfig.getMaxRounds() == null) {
            return 20;
        }
        return aiConfig.getMaxRounds() * 2;
    }


}
